/*
 * Copyright (c) 2010, VeRSI Consortium
 *   (Victorian eResearch Strategic Initiative, Australia)
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the VeRSI, the VeRSI Consortium members, nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package versi.vitro.oaiserviceprovider.service.jena.impl;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;

import org.apache.log4j.Logger;

import versi.vitro.oaiserviceprovider.service.jena.api.JenaHelper;
import versi.vitro.oaiserviceprovider.service.jena.api.JenaService;
import versi.vitro.oaiserviceprovider.service.jena.api.ModelBuilder;
import versi.vitro.oaiserviceprovider.service.jena.api.SparqlQueryEngine;
import versi.vitro.oaiserviceprovider.service.jena.helper.JenaHelperImpl;

import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelMaker;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.shared.Lock;
//import com.hp.hpl.jena.vocabulary.RDF;

/**
 * Service implementing {@link JenaService}.
 */
public class JenaServiceImpl implements JenaService {

    /** log4j instance. */
    private static final Logger LOGGER = Logger.getLogger(JenaServiceImpl.class);

    /** RDF Model Maker. */
    public ModelMaker modelMaker;

    /** Jena Service Helper. */
    private JenaHelper jenaHelper;

    private ModelBuilder modelBuilder;

    /** Sparql Query Engine. */
    private SparqlQueryEngine sparqlQueryEngine;

    /**
     * Default Constructor.
     */
    public JenaServiceImpl() {
	this(null, new JenaHelperImpl(), new ModelBuilderImpl(null), new SparqlQueryEngineImpl());
    }

    /**
     * Constructor.
     * 
     * @param modelMaker
     *            JENA model maker.
     * @param jenaHelper
     *            JENA service Helper.
     * 
     */
    public JenaServiceImpl(ModelMaker modelMaker, JenaHelper jenaHelper, ModelBuilder modelBuilder,
	    SparqlQueryEngine sparqlQueryEngine) {
	super();
	this.modelMaker = modelMaker;
	this.jenaHelper = jenaHelper;
	this.modelBuilder = modelBuilder;
	this.sparqlQueryEngine = sparqlQueryEngine;
    }

    /** {@inheritDoc} */
    public JenaHelper getJenaHelper() {
	return jenaHelper;
    }

    /** {@inheritDoc} */
    public ModelMaker getModelMaker() {
	return modelMaker;
    }

    /** {@inheritDoc} */
    public void setModelMaker(final ModelMaker modelMaker) {
	this.modelMaker = modelMaker;
    }

    /** {@inheritDoc} */
    public void subtractModels(final String subtractedFromModel, final String subtractedByModel,
	    final String resultModel) {

	Model sourceModel = modelBuilder.createOntologyModelFromName(subtractedFromModel);
	Model subsetModel = modelBuilder.createOntologyModelFromName(subtractedByModel);
	Model destinationModel = modelBuilder.createOntologyModelFromName(resultModel);
	destinationModel.add(sourceModel.difference(subsetModel));
    }

    /** {@inheritDoc} */
    public void createModel(final String modelName) {

	modelMaker.createModel(modelName);
    }

    /** {@inheritDoc} */
    public void deleteModel(final String modelName) {

	this.modelMaker.removeModel(modelName);
    }

    /** {@inheritDoc} */
    public void clearModel(final String modelName) {

	modelMaker.removeModel(modelName);
	modelMaker.createModel(modelName);
    }

    /** {@inheritDoc} */
    public void removeModel(final String modelName) {

	modelMaker.removeModel(modelName);
    }

    /** {@inheritDoc} */
  /*  public void renameBlankNode(final String namespaceEtcStr, final String[] sourceModelNames,
	    final String destinationModelName) {

	OntModel sourceOntologyModel = modelBuilder.createOntologyModelFromSpec(OntModelSpec.OWL_DL_MEM);
	Model destinationModel = modelBuilder.createOntologyModelFromName(destinationModelName);

	int sourceModelNameCount = sourceModelNames.length;
	if (sourceModelNameCount != 0) {
	    for (int sourceModelNameIndex = 0; sourceModelNameIndex < sourceModelNameCount; sourceModelNameIndex++) {
		Model subModel = modelBuilder.createOntologyModelFromName(sourceModelNames[sourceModelNameIndex]);
		if (isDebugEnabled()) {
		    LOGGER.debug(sourceModelNames[sourceModelNameIndex] + " model size = " + subModel.size());
		}
		sourceOntologyModel.addSubModel(subModel);
	    }
	}

	destinationModel.enterCriticalSection(Lock.WRITE);
	try {
	    destinationModel.add(jenaHelper.renameBlankNodes(sourceOntologyModel, namespaceEtcStr));
	} finally {
	    destinationModel.leaveCriticalSection();
	}
    }*/

    /** {@inheritDoc} */
    public ByteArrayOutputStream getModelAsOutputStream(final String modelName, final String lang) {

	ByteArrayOutputStream out = new ByteArrayOutputStream();

	if (jenaHelper.validateRDFFormat(lang)) {

	    Model model = modelBuilder.createOntologyModelFromName(modelName);
	    model.enterCriticalSection(Lock.READ);
	    try {
		// TODO RR check with Cyrus to remove.
		// RDFWriter writer = model.getWriter();
		// if ( lang.equals("RDF/XML") ) {
		// out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>".getBytes());
		// }

		// writer.write(model, out, lang);
		// model.write(new PrintWriter(System.out), lang);
		model.write(new PrintWriter(out), lang);

	    } catch (com.hp.hpl.jena.shared.CannotEncodeCharacterException cannotEncodeException) {
		String message = buildCantEncodeCharacterMessage(cannotEncodeException);
		LOGGER.fatal(message, cannotEncodeException);
		throw new RuntimeException(message, cannotEncodeException);

	    } catch (Exception exception) {
		String message = "Unable to render a model as an output stream. ";
		LOGGER.fatal(message, exception);
		throw new RuntimeException(message, exception);
	    } finally {
		try {
		    // RR: check JavaDocs-- probably not needed for
		    // ByteArrayOutputStream.
		    out.flush();
		    out.close();
		} catch (IOException ignore) {
		}

		model.leaveCriticalSection();
	    }
	}
	return out;
    }

    /**
     * 
     * @param cannotEncodeException
     * @return
     */
    private String buildCantEncodeCharacterMessage(
	    com.hp.hpl.jena.shared.CannotEncodeCharacterException cannotEncodeException) {
	byte[] badCharBytes = String.valueOf(cannotEncodeException.getBadChar()).getBytes();
	String message = "Cannot encode the character whose decimal representation is: ";
	int byteCount = badCharBytes.length;
	String delimiter = "";
	for (int byteIndex = 0; byteIndex < byteCount; byteIndex++) {
	    message += delimiter + badCharBytes[byteIndex];
	    delimiter = ", ";
	}
	return message;
    }

    /** {@inheritDoc} */
    public void populateModelwithSPARQL(final String sparql, final String[] sourceModelNames,
	    final String destinationModelName, final boolean useDefaultSource) {

	OntModel sourceOntologyModel = modelBuilder.createOntologyModelFromSpec(OntModelSpec.OWL_DL_MEM);
	Model destinationModel = modelBuilder.createOntologyModelFromName(destinationModelName);

	if (useDefaultSource) {
	    addDefaultSourceModels(sourceOntologyModel);
	}

	int sourceModelNameCount = sourceModelNames.length;
	if (sourceModelNameCount != 0) {
	    for (int sourceModelNameIndex = 0; sourceModelNameIndex < sourceModelNameCount; sourceModelNameIndex++) {
		Model subModel = modelBuilder.createOntologyModelFromName(sourceModelNames[sourceModelNameIndex]);

		if (isDebugEnabled()) {
		   LOGGER.debug(sourceModelNames[sourceModelNameIndex] + " model size = " + subModel.size());
		}
		sourceOntologyModel.addSubModel(subModel);
	    }
	}
	sparqlQueryEngine.populate(sparql, sourceOntologyModel, destinationModel);
    }

    /**
     * Add and return default source models.
     * 
     * @param ontologyModel
     *            Aggregated Model.
     * @return OntModel Aggregated Model of all the default sources model.
     */
    protected OntModel addDefaultSourceModels(final OntModel ontologyModel) {

	ontologyModel.addSubModel(modelBuilder.createOntologyModelFromName("vitro:jenaOntModel"));
	ontologyModel.addSubModel(modelBuilder.createOntologyModelFromName("vitro:baseOntModel"));
	ontologyModel.addSubModel(modelBuilder
		.createOntologyModelFromName("http://vitro.mannlib.cornell.edu/default/vitro-kb-2"));
	// TODO RR check with Cyrus to remove.
	// model.addSubModel(getModel("http://vitro.mannlib.cornell.edu/default/vitro-kb-inf"));
	if (isDebugEnabled()) {
	    LOGGER.debug("default source model size = " + ontologyModel.size());
	}
	return ontologyModel;
    }

    /** {@inheritDoc} */
    public void addRDF(final String modelName, final String subject, final String predicate, final String object) {

	final Model ontologyModel = modelBuilder.createOntologyModelFromName(modelName);
	Resource s = ontologyModel.createResource(subject);
	Property p = ontologyModel.createProperty(predicate);
	// Resource o = ontologyModel.createResource(object);
	Literal o = ontologyModel.createLiteral(object);

	Statement statement = ontologyModel.createStatement(s, p, o);
	ontologyModel.add(statement);
	// ontologyModel.commit();
	// modelMaker.createModel(modelName).add(statement);

    }

    /** {@inheritDoc} */
    public void removeRDF(final String modelName, final String subject, final String predicate, final String object) {

	final Model ontologyModel = modelBuilder.createOntologyModelFromName(modelName);
	//Resource s = ontologyModel.createResource(subject);
	//Property p = ontologyModel.createProperty(predicate);
	Resource resource= ontologyModel.getResource(subject); 
	StmtIterator iterator = resource.listProperties();
	
	Property property;
	Statement statement;

	    LOGGER.info("iterator start");
	while (iterator.hasNext()) {
	    
	    LOGGER.info("iterator has next");
		statement = (Statement) iterator.next();
		property = statement.getPredicate();
		LOGGER.info("property = "+property);
		if (property.equals(predicate)) {
			resource.getModel().remove(statement);
			//resource.addProperty(RDF.type, newType);
		}

	}


	
	//RDFNode  o = ontologyModel.createLiteral(object);
	//Resource o = ontologyModel.createResource(object);
	//Literal o = ontologyModel.createLiteral(object);

	//RDFNode o = 
	//Statement statement = ontologyModel.createStatement(s, p,o);
	
	//Statement statement = ontologyModel.createStatement(s, p, o);
	//ontologyModel.remove(statement);
	// ontologyModel.commit();
	// modelMaker.createModel(modelName).add(statement);

    }

    /** {@inheritDoc} */
    public byte[] executeSparqlQuery(final String sparql, final String sourceModels) {

	//Model sourceModel= null;
	if (sparql == null) {
	    LOGGER.error("SPARQL statement cannot be empty.");
	    throw new RuntimeException();

	}
	if (sourceModels.length() == 0) {
	    LOGGER.error("Must select at least one source model.");
	    throw new RuntimeException();

	}
	
	//for (int i = 0; i < sourceModels.length; i++) {
	    Model tempModel = modelMaker.getModel(sourceModels);
	    // for( String tempModelName : sourceModels)
	    // Model tempModel = modelMaker.getModel(tempModelName);
	  //  sourceModel.add(tempModel);
	//}
	return sparqlQueryEngine.executeSparqlReturnCSV(sparql, tempModel);
    }

    /*   *//** {@inheritDoc} */
    /*
     * public String[] getRDF(final String modelName, final String subject,
     * final String predicate, final String object) {
     * 
     * // TODO RR check with Cyrus to remove. // getModel(modelName).get
     * 
     * Resource s = getModel(modelName).createResource(subject); Property p =
     * getModel(modelName).createProperty(predicate); RDFNode o =
     * getModel(modelName).createLiteral(object);
     * 
     * Statement statement = getModel(modelName).createStatement(s, p, o);
     * getModel(modelName).add(statement);
     * 
     * 
     * return null; }
     */
    /** {@inheritDoc} */
    public void deleteRDF(final String modelName, final String subject, final String predicate, final String object) {

	modelBuilder.createOntologyModelFromName(modelName).listStatements();
    }

    /** {@inheritDoc} */
    public void printAllModelsSize() {
	getJenaHelper().printAllModelsSize(modelMaker);
    }

    protected boolean isDebugEnabled() {
	return true;
	//return LOGGER.isDebugEnabled();
    }
}
